from __future__ import absolute_import             
import logging
from pkgr.rpm.util import compareEVR

logger = logging.getLogger('pkgr.package')

class Package(object):
    ''' A generic package object. '''
    _name = None
    _arch = None
    _version = None
    _epoch = None
    _release = None
    _summary = None
    _description = None
    _url = None
    _size_package = None
    _size_installed = None
    
    # monkeyed in repository
    _repo = None
    
    def __init__(self, repo, **kwargs):
        self._repo = repo
        self._name = kwargs.get('name')
        self._arch = kwargs.get('arch')
        self._version = kwargs.get('version')
        self._epoch = kwargs.get('epoch')
        self._release = kwargs.get('release')
    
    def get_repository(self):
        return self._repo
        
    def __repr__(self):
        return "<Package(n=%s, a=%s, v=%s, r=%s, repo=%s) at %s>" %    (
            self.get_name(), self.get_arch(), self.get_version(),
            self.get_release(), self.get_repository().get_name(), id(self)
        )
        
    def get_id(self):
        ''' Returns the shortest id a user can use to identify the package. 
        '''
        return self.get_name() +'.'+self.get_arch()
        
    def get_name(self):
        ''' Returns the package name. 
        '''
        return self._name
     
    def get_arch(self):
        ''' Returns the package architecture. 
        '''
        return self._arch
        
    def get_version(self):
        ''' Returns the package version. 
        '''
        return self._version
        
    def get_release(self):
        ''' Returns the package release. 
        '''
        return self._release
     
    def get_epoch(self):
        ''' Returns the package epoch. 
        '''
        if self._epoch is None:
            return 0
        return self._epoch
        
    def get_version_string(self):
        ''' Returns a version string for the package. 
        '''
        arr = [self.get_version()]
        if self.get_release() is not None:
            arr.append(self.get_release())
            
        return '.'.join(arr)
    
    def get_evr(self):
        ''' Returns a tuple of (epoch, version, release). 
        '''
        return (self.get_epoch(), self.get_version(), self.get_release())
        
    def compare(self, other):
        ''' Compares this packages evr against another. 
        '''
        assert self.get_name() == other.get_name()
        return compareEVR(self.get_evr(), other.get_evr())
       
    def get_all_provides(self):
        ''' Returns a list of *all* :class:`Provides` this class provides.
        '''
        raise NotImplementedError
    
    def meets_requirement(self, r):
        ''' Check if package provides requirement r.
            
            Args:
                r(:class:`~pkgr.package.Requires`): A requirement the package must provide.
            
            Returns:
                True if the package provides requirement r.
        '''
        
        #logger.debug('Checking provider %s meets requirement %s', repr(self), str(r))
        # check in header if there is an r.name in there
        requirement_name = r.get_name()
        name_provided = False
        
        if self.get_name() == requirement_name:
            name_provided = True
            
        if not name_provided:
            for prov in self.get_all_provides():
                if prov == requirement_name:
                    name_provided = True
                    break
                    
        if not name_provided:
            logger.log(5, 'RPM %s package doesnt provide name %s', self, str(r))
            return False
        
        operator = r.get_operator()
        
        selfarch = self.get_arch()
        arch = r.get_arch()
        
        # requirement is just a name, and the package provides it at this point
        if operator is None and (selfarch is None and arch is None):
            logger.log(5, 'Package %s meets req %s', self, r)
            return True
            
        elif arch is not None and arch == selfarch:
            return True
            
          
        logger.debug('Operator is %s', operator)
        # Compare versions
        res = compareEVR(self.get_evr(), r.get_evr())
        
        
        if operator is not None and operator not in ['EQ', 'GT', 'GE', 'LT', 'LE']:
            raise Exception("Unknown operator type %s" % operator)
        if operator == "EQ" and (res == 0):
            return True
        elif operator == "GT" and (res == 1):
            return True
        elif operator == "GE" and (res == 1 or res == 0):
            return True
        elif operator == "LT" and (res == -1):
            return True
        elif operator == "LE" and (res == -1 or res == 0):
            return True
        elif operator is None:
            # if the operator is none the requirement just specifies that r.get_name() be provided
            return True
            
        return False
     
    def get_obsoletes(self):
        ''' Returns a set of :class:`~pkgr.package.Obsoletes`.
        '''
        raise NotImplementedError
     
    def get_files(self):
        ''' Returns a set of :class:`~pkgr.package.Provides`.
        '''
        raise NotImplementedError
        
    def get_provides(self):
        ''' Returns a set of :class:`~pkgr.package.Provides`.
        '''
        raise NotImplementedError
    
    def get_requires(self):
        ''' Returns a set of :class:`~pkgr.package.Requires`.
        '''
        raise NotImplementedError
        
    def __hash__(self):
        return hash(self.get_id())
     
    def __eq__(self, other):
        if hash(self) == hash(other):
            return True
        return False   
        
    def __cmp__(self, other):
        return self.compare(other)   
     
    def to_requires(self):
        ''' Returns the package as if it were a :class:`~pkgr.package.Requires`. 
        '''
        return Requires(self.get_name(), "EQ", self.get_version(), self.get_release(), self.get_epoch(), self.get_arch())
        

class PackageDep(object):
    _name = None
    _flags = None
    _version = None
    _release = None
    _epoch = None
    _arch = None
    
    def __init__(self, name, flags=None, version=None, release=None, epoch=None, arch=None):
        if not isinstance(name, basestring):
            raise ValueError("name must be a basestring. got %s" % type(name))
        
        if flags is not None:
            if not isinstance(flags, basestring):
                raise ValueError("flags must be a basestring.")
        
        self._name = name
        self._flags = flags
        self._version = version
        self._release = release
        self._epoch = epoch
        self._arch = arch
      
    def get_operator(self):
        ''' Returns the operator (LT, GT, EQ, etc). '''
        return self._flags
        
    def get_name(self):
        ''' Returns the name. '''
        return self._name
     
    def get_arch(self):
        ''' Returns the architecture. '''
        return self._arch
        
    def get_version(self):
        ''' Returns the version. '''
        return self._version
        
    def get_release(self):
        ''' Returns the release string. '''
        return self._release
     
    def get_epoch(self):
        ''' Returns the epoch. '''
        return self._epoch
     
    def get_evr(self):
        ''' Returns a tuple of (epoch, version, release). '''
        return (self.get_epoch(), self.get_version(), self.get_release())
        
    def compare(self, other):
        ''' Compares one packages evr against other. 
        
            Returns:
                -1 if less, 0 if equal or 1 if greater.
        '''
        assert self.get_name() == other.get_name(), "Compared packages must have the same name!"
        return compareEVR(self.get_evr(), other.get_evr())
        
    def __hash__(self):
        return hash(self.get_name()+self.get_version_string())
       
    def __eq__(self, other):
        if self.get_name() == other.get_name() and self.compare(other) == 0:
            return True
        return False
        
    def get_operator_symbol(self):
        flagstr = ""
        flags = self.get_operator()
        if flags == "GT": 
            flagstr = ">"
        elif flags == "GE": 
            flagstr = ">="
        elif flags == "LT": 
            flagstr = "<"
        elif flags == "LE": 
            flagstr = "<="
        elif flags == "EQ": 
            flagstr = "=="
        elif flags is None:
            pass
        else:
            raise Exception("Unknown operator type %s" % flags)
        return flagstr
        
    def __str__(self):
        flagstr = self.get_operator_symbol()
        arr = [self.get_name()]
        if flagstr:
            arr.append(flagstr)
        v = self.get_version_string()
        if v:
            arr.append(v)
        #arr.append(str(hash(self)))
        return ''.join(arr)
    
    def __repr__(self):
        return '<%s(%s)>' % (self.__class__.__name__, str(self))
        
    def get_version_string(self):
        arr = []
        if self.get_version() is not None:
            arr.append(self.get_version())
        if self.get_release() is not None:
            arr.append('-' + self.get_release())
        if self.get_epoch() is not None:
            arr.append(':' + str(self.get_epoch()))
        if self.get_arch() is not None:
            arr.append('.' + self.get_arch())
        return ''.join(arr)
  
# disable abstract class checking here
#pylint: disable=R0921
class InstalledPackage(Package):
    pass

class RemotePackage(Package):
    pass

class Provides(PackageDep):
    ''' Used to represent something a package provides.
    '''
    pass

class Obsoletes(PackageDep):
    ''' Used to represent a package obsoletion.
    
        If a package spec includes a Obsoletes: <obsoleted_package> flag, it
        means that the obsoleted_package should be removed before installing the
        package.
    '''
    pass

class Requires(PackageDep):
    ''' Used to represent a package requirement.
    '''
    @classmethod
    def from_string(cls, string):
        """ take a list strings and match the packages in the sack against it
           this will match against:
           name
           requirement => name operator? version?
           version => ver(-rel)?(:epoch)?(.arch)?
           operator => [>=|<=|==|<|>]
        """
        r = cls(string)
        return r
        #re_requirement = re.compile('(.*?)\s*?(\>\=|\<\=|\=\=|\<|\>)?\s*?(.*)?')
        #m = re_requirement.match(string)
        #
        #if not m:
        #    raise Exception("Invalid requirement string")

